/*
 * @Author: DXY
 * @Date: 2021-01-19 16:39:43
 * @LastEditTime: 2021-01-25 16:20:36
 * @LastEditors: DXY
 * @Description:
 * @FilePath: \obs-studio-node-example\obsRecorder.js
 * @
 */
import { BrowserWindow, screen } from 'electron';
import { IScene, ISceneItem, ITransition } from 'obs-studio-node';
import path from 'path';
import { Subject } from 'rxjs';
import { first } from 'rxjs/operators';

import {
  InputSourceSettings,
  osnSelectType,
  saveModelType,
  SceneModelType,
} from '@/types';
import _ from 'lodash';
import { byOS, getOS, OS } from './operating-systems';
import { loopProperty, SceneModelCollection } from './scenesModel';

const osn = require('obs-studio-node');
const fs = require('fs');
//保存json配置
let saveData: saveModelType;
let sourceConfig: { [name: string]: any } = {};

let sourceData = new Map<string, any>();

let nwr: any;
const signals = new Subject();
// NWR在mac上通过IOSurface处理显示渲染
if (getOS() === OS.Mac) {
  nwr = require('node-window-rendering');
}

let obsInitialized = false;
let sceneList: IScene[] = [];
let intervalX = 0;
let intervalY = 0;

let sceneModels = SceneModelCollection.getInstance();

export function getSaveData() {
  return saveData;
}

function getSceneModels() {
  console.log(displayId);
  return sceneModels;
}

export function Proxy(args: { name: string; sourceMethod: string }) {
  const scene = osn.SceneFactory.fromName(sceneModels.selectScene.name);
  scene.getItems().forEach((item) => {
    if (item.source.name === args.name) {
      item.moveTop();
      // item[args.sourceMethod]()
    }
  });
}

export function exChangeSort(data: { type: 'scene' | 'source'; step: number }) {
  if (data.type === 'scene') {
    sceneModels.exChangeScene(data.step);
  } else {
    sceneModels.exChangeSource(data.step);
    const scene = osn.SceneFactory.fromName(sceneModels.selectScene.name);
    const moveTop = (name: string) => {
      scene.getItems().forEach((item) => {
        if (item.source.name === name) {
          item.moveTop();
        }
      });
    };
    const moveBottom = (name: string) => {
      scene.getItems().forEach((item) => {
        if (item.source.name === name) {
          item.moveBottom();
        }
      });
    };
    if (data.step > 0) {
      // 向下一步
      const index = sceneModels.selectScene.items.length;
      const curIndex = sceneModels.selectScene.items.findIndex(
        (item) => item.name === sceneModels.selectSource?.name,
      );
      for (let i = curIndex; i < index; i++) {
        moveTop(sceneModels.selectScene.items[i].name);
      }
    } else {
      //向上一步
      const index = sceneModels.selectScene.items.length;
      const curIndex = sceneModels.selectScene.items.findIndex(
        (item) => item.name === sceneModels.selectSource?.name,
      );
      for (let i = curIndex; i >= 0; i--) {
        moveBottom(sceneModels.selectScene.items[i].name);
      }
    }
    sceneModels.activeSource(sceneModels.selectSource!.name);
  }
}

export const isDevelopment = process.env.NODE_ENV === 'development';
export const OBS_PLUGIN_PATH = isDevelopment
  ? process.cwd()
  : path.join(process.resourcesPath, 'app.asar.unpacked');

async function test(data: any) {
  // Sce;
  const scene = osn.SceneFactory.fromName(sceneModels.selectScene.name);
  //console.log(scene.getItems().length)
  let setting = null;
  scene.getItems().map((item: any) => {
    // 删除
    if (data === item.source.name) {
      console.log('test');
      setting = {
        setting: loopProperty(item.source),
        senceItem: JSON.stringify(item),
      };
    }
  });
  return setting;
}

// 关闭软件
function shutdown() {
  if (!obsInitialized) {
    console.debug('OBS is already shut down!');
    return false;
  }

  console.debug('Shutting down OBS...');
  try {
    osn.NodeObs.OBS_service_removeCallback();
    osn.NodeObs.IPC.disconnect();
    obsInitialized = false;
  } catch (e) {
    throw Error('Exception when shutting down OBS process' + e);
  }

  console.debug('OBS shutdown successfully');

  return true;
}

export function setSetting(category: any, parameter: any, value: any) {
  let oldValue;
  // 要设置容器
  const settings = osn.NodeObs.OBS_settings_getSettings(category).data;

  settings.forEach((subCategory: any) => {
    subCategory.parameters.forEach((param: any) => {
      if (param.name === parameter) {
        oldValue = param.currentValue;
        param.currentValue = value;
      }
    });
  });

  // 保存更新的设置容器
  if (value !== oldValue) {
    osn.NodeObs.OBS_settings_saveSettings(category, settings);
  }
  if (!saveData.obsSetting.obs[category]) {
    saveData.obsSetting.obs[category] = {};
  }
  saveData.obsSetting.obs[category][parameter] = value;
}

export function setUserSetting(cate: any) {
  const keys = Object.keys(cate);
  keys.forEach((key) => {
    const category = Object.keys(cate[key]);
    category.forEach((item) => {
      setSetting(key, item, cate[key][item]);
    });
  });
}

function initObsSetting() {
  const defaultSettings = {
    sceneList: [
      {
        name: '场景一',
        selected: true,
        sort: 0,
        items: [],
      },
    ],
    audioList: [],
    defaultTransition: {
      type: 'Cut',
      timer: 300,
    },
    obsSetting: {
      customer: {},
      obs: {},
    },
  };

  const filePath = path.join(getPwd(), 'obsSetting.json');
  if (fs.existsSync(filePath)) {
    const jsonData = fs.readFileSync(filePath, 'utf-8');
    try {
      saveData = JSON.parse(jsonData);
    } catch (e) {
      saveData = defaultSettings;
    }
  } else {
    saveData = defaultSettings;
  }

  function initSetting() {
    let obsSetting = saveData.obsSetting.obs;
    console.log(obsSetting);
    let setting = Object.keys(obsSetting);
    console.log('setting', setting);
    setSetting('Output', 'Mode', 'Advanced');
    setting.forEach((settingKey) => {
      let paramKey = Object.keys(obsSetting[settingKey]);
      paramKey.forEach((paramKeyKey) => {
        console.log(
          settingKey,
          paramKeyKey,
          obsSetting[settingKey][paramKeyKey],
        );

        setSetting(
          settingKey,
          paramKeyKey,
          obsSetting[settingKey][paramKeyKey],
        );
      });
    });
  }

  initSetting();

  function busySleep(sleepDuration: number) {
    const now = new Date().getTime();
    while (new Date().getTime() < now + sleepDuration) {
      /* do nothing */
    }
  }

  function initScene() {
    // if (saveData.sceneList.length > 0) {
    //
    // } else {
    //     const scene = osn.SceneFactory.create('场景一')
    //     sceneModels.addScene(scene)
    // }
    let selectScene: string;
    sceneModels.set(JSON.parse(JSON.stringify(saveData.sceneList)));
    const selectScen = saveData.sceneList.find((item) => item.selected);

    saveData.sceneList.forEach((sceneItem, index) => {
      let scene = osn.SceneFactory.create(sceneItem.name);
      sceneModels.activeScene(sceneItem.name);
      // sceneModels.addScene(scene)
      sceneItem.items.forEach((sourceModel) => {
        let source = osn.InputFactory.create(sourceModel.id, sourceModel.name);
        let sourceItem = scene.add(source);
        // const updateItem = {
        //   position: {
        //     x: Math.floor(sourceModel.x),
        //     y: Math.floor(sourceModel.y),
        //   },
        //   scale: {
        //     x: Math.floor(sourceModel.scaleX),
        //     y: Math.floor(sourceModel.scaleY),
        //   },
        //   visible: sourceModel.visible,
        // };
        const updateItem = {
          position: {
            x: sourceModel.x || 0,
            y: sourceModel.y || 0,
          },
          scale: {
            x: sourceModel.scaleX || 1,
            y: sourceModel.scaleY || 1,
          },
          visible: sourceModel.visible,
        };

        // source.update(sourceModel.settings)
        sourceItem.moveTop();
        // sceneModels.addSource(sourceItem,sourceModel.aliasName)

        updateSourceSettings({
          sceneName: scene.name,
          name: source.name,
          updateSettings: sourceModel.settings,
        });
        updateSceneItemSettings({
          sceneName: scene.name,
          name: source.name,
          updateSettings: updateItem,
        });
        // if (true) {
        //   busySleep(400);
        // }
      });
    });

    sceneModels.activeScene(
      selectScen ? selectScen.name : saveData.sceneList[0].name,
    );
  }

  initScene();
}

const TeachConfigPath = path.join(getPwd(), 'teachConfig.json');
export const getTeachConfig = () => {
  const filePath = TeachConfigPath;
  if (fs.existsSync(filePath)) {
    const data = fs.readFileSync(filePath);
    try {
      return JSON.parse(data);
    } catch (e) {
      console.log('getTeachConfig 错误', e);
      return undefined;
    }
  } else {
    const defalt = {
      softType: '',
      videoMatrixIp: '',
      nickname: '',
      serverIp: '',
      peerServer: '',
      screenshotWorkName: '',
      m7sServerIp: '',
      obsPushServer: '',
      miniIOServer: '',
    };
    setTeachConfig(defalt);
    return defalt;
  }
};
export const setTeachConfig = (data: any) => {
  saveToJsonFile(data, TeachConfigPath);
};

const saveToFile = () => {
  try {
    console.log('执行saveObsSettingToFile');
    const filePath = path.join(getPwd(), 'obsSetting.json');
    saveData.sceneList = sceneModels.get();
    saveData.sceneList.forEach((scene) => {
      scene.selected = scene.name === sceneModels.selectScene.name;
    });
    // saveData.sceneList.find(
    //   (scene) => scene.name === sceneModels.selectScene.name,
    // )!.selected = true;
    saveData.audioList = getAudio();

    console.log('saveData', saveData);
    saveToJsonFile(saveData, filePath);
  } catch (e) {
    console.log('e', e);
  }
};
export const saveObsSettingToFile = _.debounce(saveToFile, 1000);

function getPwd() {
  return OBS_PLUGIN_PATH;
}

// 初始化obs
function initOBS() {
  console.debug('Initializing OBS...');
  // -${ new Date().getMilliseconds() }
  osn.NodeObs.IPC.host(`obs-studio-node-example` + new Date().getTime()); // 这里的uuid也可以换成时间戳
  // const osnPath = path.join(__dirname, './node_modules','obs-studio-node')
  console.log('setWorking start');
  const dicretory = path.join(getPwd(), 'node_modules', 'obs-studio-node');
  osn.NodeObs.SetWorkingDirectory(dicretory);
  console.log('dicretory', dicretory);
  console.log('setWorking success');

  const obsDataPath = path.join(getPwd(), 'osn-data'); // OBS Studio configs and logs
  console.log('obsDataPath', obsDataPath);

  // 参数:区域设置、存储配置和日志的目录路径、应用程序版本
  const initResult = osn.NodeObs.OBS_API_initAPI('en-US', obsDataPath, '1.0.0');
  console.log('initResult', initResult);

  if (initResult !== 0) {
    const errorReasons: any = {
      '-2': 'DirectX could not be found on your system. Please install the latest version of DirectX for your machine here <https://www.microsoft.com/en-us/download/details.aspx?id=35?> and try again.',
      '-5': 'Failed to initialize OBS. Your video drivers may be out of date, or Streamlabs OBS may not be supported on your system.',
    };

    const errorMessage: any =
      errorReasons[initResult.toString()] ||
      `An unknown error #${initResult} was encountered while initializing OBS.`;

    console.error('OBS init failure', errorMessage);

    shutdown();

    throw Error(errorMessage);
  }

  osn.NodeObs.OBS_service_connectOutputSignals((signalInfo: any) => {
    signals.next(signalInfo);
  });

  console.debug('OBS initialized');
}

function getAvailableValues(category: any, subcategory: any, parameter: any) {
  const categorySettings = osn.NodeObs.OBS_settings_getSettings(category).data;

  if (!categorySettings) {
    console.warn(`There is no category ${category} in OBS settings`);
    return [];
  }

  const subcategorySettings = categorySettings.find(
    (sub: any) => sub.nameSubCategory === subcategory,
  );
  if (!subcategorySettings) {
    console.warn(
      `There is no subcategory ${subcategory} for OBS settings category ${category}`,
    );
    return [];
  }

  const parameterSettings = subcategorySettings.parameters.find(
    (param: any) => param.name === parameter,
  );
  if (!parameterSettings) {
    console.warn(
      `There is no parameter ${parameter} for OBS settings category ${category}.${subcategory}`,
    );
    return [];
  }

  return parameterSettings.values.map((value: any) => Object.values(value)[0]);
}

function configureOBS() {
  console.debug('Configuring OBS');

  console.debug('OBS Configured');
  initObsSetting();
}

function busySleep(sleepDuration: any) {
  const now = new Date().getTime();
  while (new Date().getTime() < now + sleepDuration) {
    /* do nothing */
  }
}

// 获取关于主显示器的信息
function displayInfo() {
  const { screen } = require('electron');

  const primaryDisplay = screen.getPrimaryDisplay();
  const { width, height } = primaryDisplay.size;
  const { scaleFactor } = primaryDisplay;
  return {
    width,
    height,
    scaleFactor: scaleFactor,
    aspectRatio: width / height,
    physicalWidth: width * scaleFactor,
    physicalHeight: height * scaleFactor,
  };
}

// 获取照相机数据
function getCameraSource() {
  console.debug('Trying to set up web camera...');
  // 安装输入不初始化任何设备，只是获得可用的列表
  const dummyInput = byOS({
    [OS.Windows]: () =>
      osn.InputFactory.create('dshow_input', 'video', {
        audio_device_id: 'does_not_exist',
        video_device_id: 'does_not_exist',
      }),
    [OS.Mac]: () =>
      osn.InputFactory.create('av_capture_input', 'video', {
        device: 'does_not_exist',
      }),
  });

  const cameraItems = dummyInput.properties.get(
    byOS({ [OS.Windows]: 'video_device_id', [OS.Mac]: 'device' }),
  ).details.items;
  console.debug(cameraItems);
  dummyInput.release();

  if (cameraItems.length === 0) {
    console.debug('No camera found!!');
    return null;
  }

  const deviceId = cameraItems[0].value;
  cameraItems[0].selected = true;
  console.debug('cameraItems[0].name: ' + cameraItems[0].name);

  const obsCameraInput = byOS({
    [OS.Windows]: () =>
      osn.InputFactory.create('dshow_input', 'video', {
        video_device_id: deviceId,
      }),
    [OS.Mac]: () =>
      osn.InputFactory.create('av_capture_input', 'video', {
        device: deviceId,
      }),
  });

  // It's a hack to wait a bit until device become initialized (maximum for 1 second)
  // If you know proper way how to determine whether camera is working and how to subscribe for any events from it, create a pull request
  // See discussion at https://github.com/Envek/obs-studio-node-example/issues/10
  for (let i = 1; i <= 4; i++) {
    if (obsCameraInput.width === 0) {
      const waitMs = 100 * i;
      console.debug(`Waiting for ${waitMs}ms until camera get initialized.`);
      busySleep(waitMs); // We can't use async/await here
    }
  }

  if (obsCameraInput.width === 0) {
    console.debug(
      `Found camera "${cameraItems[0].name}" doesn't seem to work as its reported width is still zero.`,
    );
    return null;
  }

  // Way to update settings if needed:
  // let settings = obsCameraInput.settings;
  // console.debug('Camera settings:', obsCameraInput.settings);
  // settings['width'] = 320;
  // settings['height'] = 240;
  // obsCameraInput.update(settings);
  // obsCameraInput.save();

  return obsCameraInput;
}

// 桌面共享处理
function setupScene() {
  // const videoSource = osn.InputFactory.create(byOS({ [OS.Windows]: 'monitor_capture', [OS.Mac]: 'display_capture' }), 'desktop-video');

  const videoSource = osn.InputFactory.create(
    'monitor_capture',
    'desktop-video',
  );
  // console.debug('videoSource', videoSource)

  const { physicalWidth, physicalHeight, aspectRatio } = displayInfo();

  // 更新源设置:
  let settings = videoSource.settings;
  // 这个参数修改使用哪个显示器,从0开始
  settings['monitor'] = 0;
  settings['width'] = physicalWidth;
  settings['height'] = physicalHeight;
  videoSource.update(settings);
  videoSource.save();

  // 设置输出视频大小为1920x1080
  const outputWidth = 1920;
  const outputHeight = Math.round(outputWidth / aspectRatio);
  // setSetting('Video', 'Base', `${outputWidth}x${outputHeight}`);
  // setSetting('Video', 'Output', `${outputWidth}x${outputHeight}`);
  const videoScaleFactor = physicalWidth / outputWidth;

  // 这里需要一个场景来适当缩放捕获的屏幕大小以输出视频大小
  const scene: IScene = osn.SceneFactory.create('test-scene');
  const sceneItem = scene.add(videoSource);
  sceneItem.scale = { x: 1.0 / videoScaleFactor, y: 1.0 / videoScaleFactor };

  // 如果相机可用，使它的宽度为视频的1/3，并把它放在显示器的正下角
  const cameraSource = getCameraSource();
  console.debug('cameraSource实例', cameraSource);
  if (cameraSource) {
    const cameraItem = scene.add(cameraSource);
    const cameraScaleFactor = 1.0 / ((3.0 * cameraSource.width) / outputWidth);
    cameraItem.scale = { x: cameraScaleFactor, y: cameraScaleFactor };
    cameraItem.position = {
      x:
        outputWidth - cameraSource.width * cameraScaleFactor - outputWidth / 10,
      y:
        outputHeight -
        cameraSource.height * cameraScaleFactor -
        outputHeight / 10,
    };
    cameraItem.moveTop();
  }

  scene.save();

  sceneList.push(scene);
  sceneModels.addScene(scene);
  const imageScene = osn.SceneFactory.create('imageScene');
  const imagesource = osn.InputFactory.create('image_source', 'logo', {
    file: 'http://lims-pro.hfibms.com/img/infrastructure-i.png',
  });
  loopProperty(imagesource);
  const imageItem = imageScene.add(imagesource);
  imageItem.position = {
    x: 500,
    y: 500,
  };
  imageItem.scale = {
    x: 10,
    y: 10,
  };
  imageItem.moveTop();
  sceneList.push(imageScene);
  const addResult = sceneModels.addScene(imageScene);
  console.log('sceneModels', JSON.stringify(addResult));
  return sceneList;
}

function getAudioDevices(type: string, subtype: any) {
  const dummyDevice = osn.InputFactory.create(type, subtype, {
    device_id: 'does_not_exist',
  });
  console.log('dummyDevice===');
  loopProperty(dummyDevice);
  console.log('dummyDevice===end');
  const devices = dummyDevice.properties
    .get('device_id')
    .details.items.map(({ name, value }) => {
      return { device_id: value, name };
    });
  dummyDevice.release();
  return devices;
}

export enum ETransitionType {
  Cut = 'cut_transition',
  Fade = 'fade_transition',
  Swipe = 'swipe_transition',
  Slide = 'slide_transition',
  FadeToColor = 'fade_to_color_transition',
  LumaWipe = 'wipe_transition',
  Stinger = 'obs_stinger_transition',
  Motion = 'motion_transition',
}

let defaultTransition: ITransition;

//
function changeScene(sceneName: string, sourceName: string) {
  // const old = oldName ?? sceneModels.selectScene.name
  if (sceneModels.selectSource) {
    setSourceSelect(sceneModels.selectSource.name, false);
    sceneModels.selectSource.selected = false;
  }
  const newSence = osn.SceneFactory.fromName(sceneName);
  sceneModels.activeScene(sceneName);
  sceneModels.activeSource(null);
  osn.Global.setOutputSource(0, defaultTransition);
  console.log('timer', saveData.defaultTransition.timer);

  defaultTransition.start(saveData.defaultTransition.timer, newSence);
}

export const saveImg = (filePath: string) => {
  if (fs.existsSync(filePath)) {
    const file = path.parse(filePath);
    const obsTemp = path.join(getPwd(), '/public/tempStorage');
    console.log('obsTemp', obsTemp);
    if (!fs.existsSync(obsTemp)) {
      fs.mkdirSync(obsTemp);
    }
    const newFile = path.join(
      obsTemp,
      file.name + '-' + new Date().getTime() + '.' + file.ext,
    );
    fs.copyFileSync(filePath, newFile);
    return newFile;
  }
};
const transStaticName: string = 'staticTrans';

function updateTransition(transType: string, timer: number) {
  const transtion = osn.TransitionFactory.create(
    ETransitionType[transType],
    transStaticName,
  );
  const scene = osn.SceneFactory.fromName(sceneModels.selectScene.name);
  transtion.set(scene);
  transtion.start(0, scene);

  osn.Global.setOutputSource(0, transtion);

  defaultTransition.release();
  defaultTransition.remove();
  defaultTransition = transtion;
  saveData.defaultTransition.type = transType;
  saveData.defaultTransition.timer = timer;
  return transType;
}

export function getAudio(): any[] {
  let result: any[] = [];
  console.log('getAudio', sourceData);

  sourceData.forEach((value, key) => {
    console.log(value, key);

    result.push({
      name: key,
      deflection: value.fader.deflection * 100,
    });
  });
  return result;
}

function setupAudio(data: { name: string; value: number }) {
  sourceData.get(data.name).fader.deflection = data.value;

  // let monitor: EMonitoringType
  // switch (data.type) {
  //     case 0:
  //         monitor = EMonitoringType.None
  //         break;
  //     case 1:
  //         monitor = EMonitoringType.MonitoringOnly
  //         break;
  //     default:
  //         monitor = EMonitoringType.MonitoringAndOutput
  //         break;
  // }
  // const desk = osn.InputFactory.fromName('desktop-audio')
  // const mic = osn.InputFactory.fromName('mic-audio')
  // console.log('desk volume', desk.volume)
  // console.log('mic volume', mic.volume)
  // console.log('desk volume', desk.volume)
  // console.log('mic volume', mic.volume)
  //
  // console.log('desk monitoringType', desk.monitoringType)
  // console.log('mic monitoringType', mic.monitoringType)
  // console.log('desk monitoringType', desk.monitoringType)
  // console.log('mic monitoringType', mic.monitoringType)

  // // desk.volume = data.volume
  // desk.monitoringType = data.type
  // // mic.volume = data.volume
  // mic.monitoringType = data.type
  //
  // let setting
  // setting = loopProperty(desk)
  // setting['volume'] = data.volume
  // setting['monitoringType'] = data.type
  // desk.update(setting)
  // setting = loopProperty(mic)
  // setting['volume'] = data.volume
  // setting['monitoringType'] = data.type
  // mic.update(setting)
  return data;
}

// 摄像头（调用照相机）
function setupSources() {
  // const audioSource = osn.InputFactory.create('wasapi_output_capture', 'desktop-audio');
  // const micSource = osn.InputFactory.create('wasapi_input_capture', 'mic-audio');
  // // Tell recorder to use this source (I'm not sure if this is the correct way to use the first argument `channel`)
  // osn.Global.setOutputSource(1, scene);
  // osn.Global.setOutputSource(2, audioSource);
  // osn.Global.setOutputSource(3, micSource);
  let lister = 0;
  let defaultTransType: ETransitionType = saveData.defaultTransition.type
    ? ETransitionType[saveData.defaultTransition.type]
    : ETransitionType.Cut;
  defaultTransition = osn.TransitionFactory.create(
    defaultTransType,
    transStaticName,
  );
  let scene: IScene = null;
  const scenes: SceneModelType[] = sceneModels.get();
  if (scenes && scenes.length > 0) {
    const selectName = sceneModels.selectScene.name;
    scene = osn.SceneFactory.fromName(selectName);
    sceneModels.activeScene(selectName);
    defaultTransition.set(scene);
  } else {
  }
  osn.Global.setOutputSource(lister, defaultTransition);
  lister++;
  setSetting('Output', 'Track1Name', 'Mixed: all sources');
  let currentTrack = lister;
  getAudioDevices(
    byOS({
      [OS.Windows]: 'wasapi_output_capture',
      [OS.Mac]: 'coreaudio_output_capture',
    }),
    'desktop-audio',
  ).forEach((metadata) => {
    console.log('metadata.device_id', metadata.device_id);
    if (metadata.device_id === 'default') return;
    const source = osn.InputFactory.create(
      byOS({
        [OS.Windows]: 'wasapi_output_capture',
        [OS.Mac]: 'coreaudio_output_capture',
      }),
      'desktop-audio',
      { device_id: metadata.device_id },
    );
    setSetting('Output', `Track${currentTrack}Name`, metadata.name);
    source.audioMixers = 1 | (1 << (currentTrack - 1)); // 位掩码只输出到轨道1和当前轨道
    const obsVolmeter = osn.VolmeterFactory.create(1);
    obsVolmeter.attach(source);
    console.log('source.name', source.name);
    const obsFader = osn.FaderFactory.create(1);
    obsFader.attach(source);

    const audioConfig = saveData.audioList.find(
      (item) => item.name === 'desktop-audio',
    );
    obsFader.deflection = audioConfig ? audioConfig.deflection : 1;

    sourceData.set(source.name, {
      volmeter: obsVolmeter,
      fader: obsFader,
    });

    osn.Global.setOutputSource(currentTrack, source);
    currentTrack++;
  });
  // monitoringType: EMonitoringType;
  getAudioDevices(
    byOS({
      [OS.Windows]: 'wasapi_input_capture',
      [OS.Mac]: 'coreaudio_input_capture',
    }),
    'mic-audio',
  ).forEach((metadata: any) => {
    console.log('metadata.device_id', metadata.device_id);
    if (metadata.device_id === 'default') return;
    const source = osn.InputFactory.create(
      byOS({
        [OS.Windows]: 'wasapi_input_capture',
        [OS.Mac]: 'coreaudio_input_capture',
      }),
      'mic-audio',
      { device_id: metadata.device_id },
    );
    setSetting('Output', `Track${currentTrack}Name`, metadata.name);
    source.audioMixers = 1 | (1 << (currentTrack - 1)); // 位掩码只输出到轨道1和当前轨道
    const obsVolmeter = osn.VolmeterFactory.create(1);
    obsVolmeter.attach(source);

    const obsFader = osn.FaderFactory.create(1);
    obsFader.attach(source);
    const audioConfig = saveData.audioList.find(
      (item) => item.name === 'mic-audio',
    );
    obsFader.deflection = audioConfig ? audioConfig.deflection : 1;

    sourceData.set(source.name, {
      volmeter: obsVolmeter,
      fader: obsFader,
    });
    osn.Global.setOutputSource(currentTrack, source);
    currentTrack++;
  });

  setSetting('Output', 'RecTracks', parseInt('1'.repeat(currentTrack - 1), 2)); // Bit mask of used tracks: 1111 to use first four (from available six)
}

let displayId = 'display1';
let showDisplayMain = false;

//切换页面的时候 隐藏display1画布
export function changeLocation(data: { name: string }) {
  showDisplayMain = data.name === '我的直播';
  if (!showDisplayMain && obsInitialized) {
    osn.NodeObs.OBS_content_resizeDisplay(displayId, 0, 0);
  }
  return showDisplayMain;
}

export function getSourceOption(type: string) {
  //暂存初始化源选项
  //   let typeList = osn.InputFactory.types()
  //   console.log('typeList', typeList)
  //   typeList.splice(0,1)
  //   typeList.splice(5,1)

  const source = osn.InputFactory.create(type, 'not_exit' + Date.now());
  const settings = loopProperty(source);
  console.log('settings', settings);
  source.release();
  source.remove();

  return settings;
}

export function closePreviewChild(displayName: string) {
  osn.NodeObs.OBS_content_destroyDisplay(displayName);
  // osn.NodeObs.OBS_content_resizeDisplay(
  //     displayName,
  //     0 ,
  //     0 ,
  // );
  return displayName;
}

export function setupPreviewChild(
  window: any,
  bounds: any,
  displayName: string,
) {
  console.log('setupPreviewChild', window, bounds, displayName);
  console.log('sceneModels.selectSource?.name', sceneModels.selectSource?.name);
  osn.NodeObs.OBS_content_createSourcePreviewDisplay(
    window.getNativeWindowHandle(),
    sceneModels.selectSource?.name, // 或者这里使用camera source Id
    displayName,
  );
  osn.NodeObs.OBS_content_setShouldDrawUI(displayName, true);
  osn.NodeObs.OBS_content_setPaddingSize(displayName, 0);
  // 填充颜色与主窗口背景颜色匹配
  osn.NodeObs.OBS_content_setPaddingColor(displayName, 46, 71, 126);
  const result = resizePreviewChild(window, bounds, displayName);
  return result;
}

export function resizePreviewChild(
  window: any,
  bounds: any,
  displayName: string,
) {
  let { aspectRatio, scaleFactor } = displayInfo();
  if (getOS() === OS.Mac) {
    scaleFactor = 1;
  }

  getSetting('', '');
  let displayWidth = Math.floor(bounds.width / 2);
  let displayHeight = Math.round(displayWidth / aspectRatio);
  let displayX = Math.floor(bounds.width / 4);
  let displayY = Math.floor(bounds.y);
  osn.NodeObs.OBS_content_resizeDisplay(
    displayName,
    displayWidth,
    displayHeight,
  );
  osn.NodeObs.OBS_content_moveDisplay(displayName, displayX, displayY);
  console.log('displayHeight', displayHeight);

  return {
    height: displayHeight,
    width: displayWidth,
    x: displayX * scaleFactor,
    y: displayY * scaleFactor,
  };
}

function setupPreview(window: any, bounds: any, name?: string) {
  osn.NodeObs.OBS_content_createDisplay(
    window.getNativeWindowHandle(),
    displayId,
    0,
    false,
  );
  osn.NodeObs.OBS_content_setShouldDrawUI(displayId, true);
  osn.NodeObs.OBS_content_setPaddingSize(displayId, 0);
  // 填充颜色与主窗口背景颜色匹配
  osn.NodeObs.OBS_content_setPaddingColor(displayId, 46, 71, 126);
  const result = resizePreview(window, bounds);
  return result;
}

let initY = 0;

function resizePreview(window: any, bounds: any) {
  let { scaleFactor } = displayInfo();
  if (getOS() === OS.Mac) {
    scaleFactor = 1;
  }
  const videoSetting = getAllSetting(['Video']);
  const aspect = videoSetting.get('Video.Base').currentValue.split('x');

  console.log('aspect:', aspect);
  const [screenWidth, screenHeight] = aspect;
  const aspectRatio = Number.parseFloat(
    (screenWidth / screenHeight).toFixed(2),
  );
  console.log('resizePreview:', bounds);
  let displayHeight = Math.floor(bounds.height);
  console.log('displayHeight', displayHeight);
  console.log('aspectRatio', aspectRatio);

  console.log('scaleFactor', scaleFactor);
  let displayWidth = Math.round(displayHeight * aspectRatio);
  displayHeight = Math.floor(displayWidth / aspectRatio);
  let displayX = Math.round((bounds.width - displayWidth) / 2);
  let displayY = Math.round(bounds.y);

  if (bounds.width > bounds.height) {
  }

  // bounds.width = bounds.width * 0.5; //显示宽度为屏幕的一半
  // let displayWidth = Math.floor(bounds.width);
  // console.log(
  //   '-----------------displayWidth,aspectRatio',
  //   displayWidth,
  //   aspectRatio,
  // );
  // let displayHeight = Math.round(displayWidth / aspectRatio);
  // displayWidth = displayHeight * aspectRatio;
  // let displayX = Math.floor(bounds.width / 2);
  // let displayY = Math.floor(bounds.y);

  if (initY === 0) {
    initY = displayY;
  }
  if (showDisplayMain) {
    osn.NodeObs.OBS_content_resizeDisplay(
      displayId,
      displayWidth * scaleFactor,
      displayHeight * scaleFactor,
    );
  } else {
    osn.NodeObs.OBS_content_resizeDisplay(displayId, 0, 0);
  }
  osn.NodeObs.OBS_content_moveDisplay(
    displayId,
    displayX * scaleFactor,
    displayY * scaleFactor,
  );
  console.log('displayHeight', displayHeight);
  const resizeData = {
    height: displayHeight,
    width: displayWidth,
    x: displayX * scaleFactor,
    y: displayY * scaleFactor,
  };
  console.log('resizeData', resizeData);

  // osn.NodeObs.OBS_content_setPaddingSize(displayId, 400);
  console.log('resizePreview', {
    height: displayHeight,
    width: displayWidth,
    x: displayX * scaleFactor,
    y: displayY * scaleFactor,
  });
  return {
    height: displayHeight,
    width: displayWidth,
    x: displayX * scaleFactor,
    y: displayY * scaleFactor,
  };
}

function getNextSignalInfo() {
  return new Promise((resolve, reject) => {
    signals.pipe(first()).subscribe((signalInfo) => resolve(signalInfo));
    setTimeout(() => reject('Output signal timeout'), 30000);
  });
}

// 设置显示器
function selectDisPlay(index: any) {
  const scene = osn.SceneFactory.fromName('test-scene');
  //console.log(scene.getItems().length)

  scene.getItems().map((item: any) => {
    //console.log(item.source.name)
    // 删除
    if ('desktop-video' === item.source.name) {
      osn.InputFactory.fromName(item.source.name).release();
      item.remove();
    }
    return item;
  });
  const videoSource = osn.InputFactory.create(
    'monitor_capture',
    'desktop-video',
  );

  const { physicalWidth, physicalHeight, aspectRatio } = displayInfo(index.id);
  // Update source settings:
  let settings = videoSource.settings;

  // 这个参数修改使用哪个显示器,从0开始
  settings['monitor'] = parseInt(index.id);
  settings['width'] = physicalWidth;
  settings['height'] = physicalHeight;
  videoSource.update(settings);
  videoSource.save();

  const newitem = scene.add(videoSource);
  const outputWidth = 1920;
  const videoScaleFactor = physicalWidth / outputWidth;
  const outputHeight = Math.round(outputWidth / aspectRatio);
  setSetting('Video', 'Base', `${outputWidth}x${outputHeight}`);
  setSetting('Video', 'Output', `${outputWidth}x${outputHeight}`);
  newitem.scale = { x: 1.0 / videoScaleFactor, y: 1.0 / videoScaleFactor };
  newitem.moveBottom();

  scene.save();
  sceneModels.addScene(scene);
  return scene;
}

function saveToJsonFile(data, filePath) {
  try {
    const jsonData = JSON.stringify(data, null, 2);
    // fs.writeFileSync(filePath, jsonData, 'utf-8');
    fs.writeFile(filePath, jsonData, 'utf8', (err) => {
      if (err) {
        console.error('写入文件时发生错误：', err);
      } else {
        console.log('文件写入成功！');
      }
    });
  } catch (error) {
    console.error('Error saving data to JSON file:', error);
  }
}

// id 类型  name名字
const createSource = (sourceParam: {
  sceneName: string;
  id: string;
  name: string;
}) => {
  const { sceneName, id, name } = sourceParam;
  const scene = osn.SceneFactory.fromName(sceneName);
  const source = osn.InputFactory.create(id, name + Date.now());
  const senceItem = scene.add(source);
  senceItem.moveTop();
  console.log('createSource   sourceParam', sourceParam);
  sceneModels.addSource(senceItem, name);
  console.log(
    'createSource   sceneModels.selectSource',
    sceneModels.selectSource?.name,
  );
  if (sceneModels.selectSource) {
    // setSourceSelect(sceneModels.selectSource?.name, true)
    scene.getItems().forEach((item) => {
      if (item.source.name === name) {
        item.selected = true;
      }
    });
  }
  return sceneModels.selectSource;
};

const setSourceSelect = (sourceName: string, selected: boolean) => {
  const scene = osn.SceneFactory.fromName(sceneModels.selectScene.name);
  scene.getItems().forEach((item) => {
    if (item.source.name === sourceName) {
      item.selected = selected;
    }
  });
};

const createScene = (sceneName: string) => {
  const scene = osn.SceneFactory.create(sceneName);
  //取消源选中
  if (sceneModels.selectSource) {
    setSourceSelect(sceneModels.selectSource.name, false);
  }
  sceneModels.addScene(scene);

  defaultTransition.release();
  defaultTransition.remove();
  defaultTransition = osn.TransitionFactory.create(
    ETransitionType.Fade,
    'staticTrans',
  );
  defaultTransition.set(scene);
  osn.Global.setOutputSource(0, defaultTransition);

  defaultTransition.start(saveData.defaultTransition?.timer ?? 300, scene);
  return sceneName;
};
const removeScene = (sceneName: string) => {
  const scene = osn.SceneFactory.fromName(sceneName);
  scene.remove();
  sceneModels.removeScene(sceneName);
  return sceneName;
};
const removeSource = (sourceParam: { sceneName: string; name: string }) => {
  sceneModels.removeSource(sourceParam);

  console.log('removeSource', sourceParam);
  const scene = osn.SceneFactory.fromName(sourceParam.sceneName);
  console.log('sceneName', scene.name);
  console.log('sceneModels.selectSource', sceneModels.selectSource);
  scene.getItems().forEach((item) => {
    if (item.source.name === sourceParam.name) {
      console.log(item.source.name, item.id);
      item.source.remove();
      item.remove();
    }
  });
  return sourceParam.name;
};
const getSourceSettings = (sourceParam: {
  sceneName: string;
  name: string;
}) => {
  return sceneModels.getScene(sourceParam.sceneName)?.items!.find((item) => {
    return item.name === sourceParam.name;
  });
};
const updateSourceSettings = (updateConfig: {
  sceneName: string;
  name: string;
  updateSettings: InputSourceSettings;
}) => {
  const scene = osn.SceneFactory.fromName(updateConfig.sceneName);
  scene.getItems().forEach((item) => {
    if (item.source.name === updateConfig.name) {
      let setting = loopProperty(item.source);
      const sourceSetting = { ...setting, ...updateConfig.updateSettings };
      console.log('updateSourceSettings   sourceSetting', sourceSetting);
      item.source.update(sourceSetting);
      let source = sceneModels.getSource(
        updateConfig.sceneName,
        updateConfig.name,
      );
      sceneModels.settingProperty(
        source?.settings,
        updateConfig.updateSettings,
      );
    }
  });

  return updateConfig.name;
};

const updateSceneItemSettings = (updateConfig: {
  sceneName: string;
  name: string;
  updateSettings: { [name: 'visible' | 'position' | 'scale']: any };
}) => {
  const scene = osn.SceneFactory.fromName(updateConfig.sceneName);
  scene.getItems().forEach((item) => {
    if (item.source.name === updateConfig.name) {
      for (const itemKey in updateConfig.updateSettings) {
        console.log(itemKey, updateConfig.updateSettings[itemKey]);
        item[itemKey] = updateConfig.updateSettings[itemKey];
      }
    }
  });

  let source = sceneModels.getSource(updateConfig.sceneName, updateConfig.name);
  sceneModels.settingProperty(source, updateConfig.updateSettings);

  return updateConfig.name;
};

function getAllWindow() {
  const capture = osn.InputFactory.create(
    'window_capture',
    'window_capture' + Date.now(),
  );
  const setting = loopProperty(capture);
  capture.release();
  capture.remove();
  return setting.window.item || setting.window.items;
}

// 获取源
function getAllSource(sceneName: string) {
  // 遍历 osn.Global.getOutputSource() 根据type判断 ESourceType === 3
  return sceneModels.getScene(sceneName)?.items;
}

// 获取场景
function getAllScene() {
  return sceneModels.get();
}

// 获取选中状态
function getOsnSelect(): osnSelectType {
  return {
    selectSource: sceneModels.selectSource,
    selectScene: sceneModels.selectScene,
  } as osnSelectType;
}

// 获取场景
function setSelected(selectData: {
  selectSource?: string;
  selectScene?: string;
}) {
  if (selectData.selectScene) sceneModels.activeScene(selectData.selectScene);
  if (selectData.selectSource)
    sceneModels.activeSource(selectData.selectSource);
}

type settingType = {
  [name: string]: any;
};

function getAllDisplays() {
  const primary = screen.getPrimaryDisplay();
  console.log(primary);

  const all = screen.getAllDisplays();
  return all;
}

// 获取设置信息
function getSetting(cate: any) {
  // console.log(electron.screen.getAllDisplays())
  return osn.NodeObs.OBS_settings_getSettings(cate.name).data;
}

// 获取设置信息
export function getAllSetting(entities: string[]) {
  const map = new Map<string, any>();
  let categories = [];
  if (!entities || entities.length === 0) {
    categories = osn.NodeObs.OBS_settings_getListCategories();
  } else {
    categories = entities;
  }
  categories.forEach((entity) => {
    const data: any[] = osn.NodeObs.OBS_settings_getSettings(entity).data;
    console.log(entity, data);
    if (data) {
      data.forEach(
        (cateItem: { nameSubCategory: string; parameters: any[] }) => {
          console.log('cateItem', cateItem);

          cateItem.parameters.forEach((item) => {
            console.log('item', item);

            map.set(`${entity}.${item.name}`, item);
          });
        },
      );
    }
  });

  return map;
}

// 获取摄像头数据
function getALlCameras() {
  const dummyInput = osn.InputFactory.create('dshow_input', 'video', {
    audio_device_id: 'does_not_exist',
    video_device_id: 'does_not_exist',
  });

  const cameraItems =
    dummyInput.properties.get('video_device_id').details.items;

  dummyInput.release();

  return cameraItems;
}

let startStreaming = false;

// 修改流地址
function udpateRtmp(window: any, settings: any) {
  // 设置流地址和key
  if (startStreaming) {
    toggleStreaming(true);
  }
  setSetting('Stream', 'server', settings.server);
  setSetting('Stream', 'key', settings.key);
  return true;
}

// 开始直播和结束直播
function toggleStreaming(state: any) {
  console.debug('streamingState:', state);
  if (!state) {
    osn.NodeObs.OBS_service_startStreaming();
    startStreaming = true;
  } else {
    osn.NodeObs.OBS_service_stopStreaming(true);
    startStreaming = false;
  }
}

// 初始化库，启动OBS Studio实例，配置它，设置源和场景
function initialize(win: BrowserWindow) {
  if (obsInitialized) {
    console.warn('OBS is already initialized, skipping initialization.');
    return;
  }
  initOBS();
  configureOBS();
  // const senceData = setupScene();
  setupSources();
  const filePath = path.join(getPwd(), 'obsSetting.json');
  if (!fs.existsSync(filePath)) {
    saveObsSettingToFile();
  }
  obsInitialized = true;

  // const perfStatTimer = setInterval(() => {
  //   win.webContents.send(
  //     'performanceStatistics',
  //     osn.NodeObs.OBS_API_getPerformanceStatistics(),
  //   );
  // }, 1000);

  // win.on('close', () => clearInterval(perfStatTimer));
}

// 开始录制
async function start(win: BrowserWindow) {
  if (!obsInitialized) initialize(win);

  let signalInfo;

  console.debug('Starting recording...');
  osn.NodeObs.OBS_service_startRecording();

  console.debug('Started?');
  signalInfo = await getNextSignalInfo();

  if (signalInfo.signal === 'Stop') {
    throw Error(signalInfo.error);
  }

  console.debug(
    'Started signalInfo.type:',
    signalInfo.type,
    '(expected: "recording")',
  );
  console.debug(
    'Started signalInfo.signal:',
    signalInfo.signal,
    '(expected: "start")',
  );
  console.debug('Started!');
}

// 结束录制
async function stop() {
  let signalInfo;

  console.debug('Stopping recording...');
  osn.NodeObs.OBS_service_stopRecording();
  console.debug('Stopped?');

  signalInfo = await getNextSignalInfo();

  console.debug(
    'On stop signalInfo.type:',
    signalInfo.type,
    '(expected: "recording")',
  );
  console.debug(
    'On stop signalInfo.signal:',
    signalInfo.signal,
    '(expected: "stopping")',
  );

  signalInfo = await getNextSignalInfo();

  console.debug(
    'After stop signalInfo.type:',
    signalInfo.type,
    '(expected: "recording")',
  );
  console.debug(
    'After stop signalInfo.signal:',
    signalInfo.signal,
    '(expected: "stop")',
  );

  console.debug('Stopped!');
}

function performanceStatistics() {
  return osn.NodeObs.OBS_API_getPerformanceStatistics();
}

//计算鼠标移动坐标
function mouseMove(
  senceSelect: string,
  sourceSelect: string,
  clientX: number,
  clientY: number,
  scaleFactor: number,
) {
  const scene = osn.SceneFactory.fromName(senceSelect);
  scene.getItems().map((item: any) => {
    if (item.source.name === sourceSelect) {
      if (scaleFactor != 0) {
        item.scale = { x: scaleFactor, y: scaleFactor };
      }
      item.position = { x: clientX - intervalX, y: clientY - intervalY };
      sceneModels.selectSource!.x = Number(item.position.x.toFixed(1));
      sceneModels.selectSource!.y = Number(item.position.y.toFixed(1));
      sceneModels.selectSource!.scaleX = Number(item.scale.x.toFixed(1));
      sceneModels.selectSource!.scaleY = Number(item.scale.y.toFixed(1));
      saveObsSettingToFile();
    }
  });
}

//选中源
function selectSource(senceSelect: string, sourceSelect: string) {
  const scene = osn.SceneFactory.fromName(senceSelect);

  scene.getItems().map((item: ISceneItem) => {
    console.log(item.source.configurable);
    if (item.source.name === sourceSelect) {
      console.log('---------------', sourceSelect);
      item.selected = true;
      // console.log('---------------',item.selected);
    } else {
      item.selected = false;
    }
  });
  sceneModels.activeSource(sourceSelect);
}

function sourceDetailMessage(bounds: any) {
  const scene = osn.SceneFactory.fromName(bounds.senceSelect);
  let result = {};
  scene.getItems().map((item: any) => {
    if (item.source.name === bounds.sourceSelectName) {
      const { x, y } = item.position;
      result = {
        sourceHeight: item.source.height * item.scale.x,
        sourceWidth: item.source.width * item.scale.x,
        sourceX: x,
        sourceY: y,
        oldScale: item.scale.x,
      };
    }
  });
  return result;
}

//计算鼠标点击坐标差值
function coordinateDifference(
  senceSelect: string,
  sourceSelect: string,
  clientX: number,
  clientY: number,
  leftMargin: number,
  scale: number,
) {
  if (clientX === 0 && clientY === 0) {
    //重置坐标差值
    intervalX = 0;
    intervalY = 0;
  } else {
    const scene = osn.SceneFactory.fromName(senceSelect);
    scene.getItems().map((item: any) => {
      if (item.source.name === sourceSelect) {
        const { x, y } = item.position;
        console.log('x,y:', x, y);
        console.log('clientX:', clientX);
        console.log('leftMargin:', leftMargin);
        console.log('scale:', scale);
        intervalX = (clientX - leftMargin) / scale - x;
        intervalY = (clientY - 190) / scale - y;
      }
    });
  }
}

//获取OBS画布
function getObsCanvasSize() {
  const videoSetting = getAllSetting(['Video']);
  const aspect = videoSetting.get('Video.Base').currentValue.split('x');

  console.log('aspect:', aspect);
  const [screenWidth, screenHeight] = aspect;
  return {
    screenWidth,
    screenHeight,
  };
}

export {
  changeScene,
  coordinateDifference,
  createScene,
  createSource,
  getALlCameras,
  getAllDisplays,
  getAllScene,
  getAllSource,
  getAllWindow,
  getObsCanvasSize,
  getOsnSelect,
  getSceneModels,
  getSetting,
  getSourceSettings,
  initialize,
  mouseMove,
  performanceStatistics,
  removeScene,
  removeSource,
  resizePreview,
  selectDisPlay,
  selectSource,
  setSelected,
  setupAudio,
  setupPreview,
  shutdown,
  sourceDetailMessage,
  start,
  stop,
  test,
  toggleStreaming,
  udpateRtmp,
  updateSceneItemSettings,
  updateSourceSettings,
  updateTransition,
};
